Index: sys/net/if_llatbl.h =================================================================== --- sys/net/if_llatbl.h +++ sys/net/if_llatbl.h @@ -151,8 +151,8 @@ typedef void (llt_free_entry_t)(struct lltable *, struct llentry *); typedef void (llt_fill_sa_entry_t)(const struct llentry *, struct sockaddr *); typedef void (llt_free_tbl_t)(struct lltable *); -typedef void (llt_link_entry_t)(struct lltable *, struct llentry *); -typedef void (llt_unlink_entry_t)(struct llentry *); +typedef int (llt_link_entry_t)(struct lltable *, struct llentry *); +typedef int (llt_unlink_entry_t)(struct llentry *); typedef void (llt_mark_used_t)(struct llentry *); typedef int (llt_foreach_cb_t)(struct lltable *, struct llentry *, void *); @@ -162,6 +162,8 @@ SLIST_ENTRY(lltable) llt_link; int llt_af; int llt_hsize; + int llt_entries; + int llt_spare; struct llentries *lle_head; struct ifnet *llt_ifp; @@ -213,6 +215,9 @@ struct sockaddr *, u_int); int lltable_sysctl_dumparp(int, struct sysctl_req *); +int htable_link_entry(struct lltable *, struct llentry *); +int htable_unlink_entry(struct llentry *); + size_t llentry_free(struct llentry *); /* helper functions */ @@ -230,8 +235,8 @@ void lltable_free_entry(struct lltable *llt, struct llentry *lle); int lltable_delete_addr(struct lltable *llt, u_int flags, const struct sockaddr *l3addr); -void lltable_link_entry(struct lltable *llt, struct llentry *lle); -void lltable_unlink_entry(struct lltable *llt, struct llentry *lle); +int lltable_link_entry(struct lltable *llt, struct llentry *lle); +int lltable_unlink_entry(struct lltable *llt, struct llentry *lle); void lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa); struct ifnet *lltable_get_ifp(const struct lltable *llt); int lltable_get_af(const struct lltable *llt); Index: sys/net/if_llatbl.c =================================================================== --- sys/net/if_llatbl.c +++ sys/net/if_llatbl.c @@ -153,14 +153,14 @@ return (error); } -static void +int htable_link_entry(struct lltable *llt, struct llentry *lle) { struct llentries *lleh; uint32_t hashidx; if ((lle->la_flags & LLE_LINKED) != 0) - return; + return (0); IF_AFDATA_WLOCK_ASSERT(llt->llt_ifp); @@ -171,14 +171,16 @@ lle->lle_head = lleh; lle->la_flags |= LLE_LINKED; CK_LIST_INSERT_HEAD(lleh, lle, lle_next); + + return (1); } -static void +int htable_unlink_entry(struct llentry *lle) { if ((lle->la_flags & LLE_LINKED) == 0) - return; + return (0); IF_AFDATA_WLOCK_ASSERT(lle->lle_tbl->llt_ifp); CK_LIST_REMOVE(lle, lle_next); @@ -187,6 +189,8 @@ lle->lle_tbl = NULL; lle->lle_head = NULL; #endif + + return (1); } struct prefix_match_data { @@ -483,6 +487,9 @@ llentry_free(lle); } + KASSERT(llt->llt_entries == 0, ("%s: lltable %p (%s) entires not 0: %d", + __func__, llt, llt->llt_ifp->if_xname, llt->llt_entries)); + llt->llt_free_tbl(llt); } @@ -608,18 +615,18 @@ llt->llt_free_entry(llt, lle); } -void +int lltable_link_entry(struct lltable *llt, struct llentry *lle) { - llt->llt_link_entry(llt, lle); + return (llt->llt_link_entry(llt, lle)); } -void +int lltable_unlink_entry(struct lltable *llt, struct llentry *lle) { - llt->llt_unlink_entry(lle); + return (llt->llt_unlink_entry(lle)); } void Index: sys/netinet/icmp6.h =================================================================== --- sys/netinet/icmp6.h +++ sys/netinet/icmp6.h @@ -635,6 +635,10 @@ uint64_t icp6s_badrs; /* bad router solicitation */ uint64_t icp6s_badra; /* bad router advertisement */ uint64_t icp6s_badredirect; /* bad redirect message */ + uint64_t icp6s_overflowdefrtr; /* Too many default routers. */ + uint64_t icp6s_overflowprfx; /* Too many prefixes. */ + uint64_t icp6s_overflownndp; /* Too many neighbour entries. */ + uint64_t icp6s_overflowredirect;/* Too many redirects. */ }; #ifdef _KERNEL Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -104,6 +104,7 @@ #include #include +#include #include #include #include @@ -122,6 +123,8 @@ offsetof(struct ifreq, ifr_ifru), "struct in6_ifreq and struct ifreq are not type punnable"); +static int in6_llt_max_entries; + VNET_DECLARE(int, icmp6_nodeinfo_oldmcprefix); #define V_icmp6_nodeinfo_oldmcprefix VNET(icmp6_nodeinfo_oldmcprefix) @@ -164,8 +167,20 @@ #define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) #define ia62ifa(ia6) (&((ia6)->ia_ifa)) +SYSCTL_DECL(_net_inet6_icmp6); +SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, in6_llt_max_entries, + CTLFLAG_RW, &in6_llt_max_entries, 0, + "Maximum number of IPv6 neighbour entries per interface. " + "0 accept unlimited."); void +in6_init(void) +{ + + in6_llt_max_entries = min(physmem / 256, 16384); +} + +void in6_newaddrmsg(struct in6_ifaddr *ia, int cmd) { struct sockaddr_dl gateway; @@ -2407,6 +2422,49 @@ return (error); } +/* + * We are overloading the if_llatbl base functions in order to check limits + * of maximum number of table entries. + * The functions return: 0 if the entry was (un)linked already and nothing + * changed, 1 if the entry was added/removed to/from the table, and -1 on + * error (e.g., not being able to add the entry due to limits reached). + * While the "unlink" operation should always succeed, callers of + * lltable_link_entry() need to check for errors and handle them. + */ +static int +in6_htable_link_entry(struct lltable *llt, struct llentry *lle) +{ + int linked; + + if (in6_llt_max_entries > 0 && + llt->llt_entries >= in6_llt_max_entries) { + ICMP6STAT_INC(icp6s_overflownndp); + return (-1); + } + + linked = htable_link_entry(llt, lle); + if (linked > 0) + llt->llt_entries += linked; + + return (linked); +} + +static int +in6_htable_unlink_entry(struct llentry *lle) +{ + struct lltable *llt; + int unlinked; + + llt = lle->lle_tbl; + KASSERT(llt->llt_entries > 0, ("%s: lltable %p (%s) entries %d <= 0", + __func__, llt, llt->llt_ifp->if_xname, llt->llt_entries)); + unlinked = htable_unlink_entry(lle); + if (unlinked > 0) + llt->llt_entries -= unlinked; + + return (unlinked); +} + static struct lltable * in6_lltattach(struct ifnet *ifp) { @@ -2425,6 +2483,9 @@ llt->llt_free_entry = in6_lltable_free_entry; llt->llt_match_prefix = in6_lltable_match_prefix; llt->llt_mark_used = in6_lltable_mark_used; + llt->llt_link_entry = in6_htable_link_entry; + llt->llt_unlink_entry = in6_htable_unlink_entry; + lltable_link(llt); return (llt); Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -352,6 +352,9 @@ #define nd_opts_last nd_opt_each.last #define nd_opts_done nd_opt_each.done +/* in6.c */ +void in6_init(void); + /* XXX: need nd6_var.h?? */ /* nd6.c */ void nd6_init(void); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -231,6 +231,8 @@ nd6_dad_init(); if (IS_DEFAULT_VNET(curvnet)) { + in6_init(); + lle_event_eh = EVENTHANDLER_REGISTER(lle_event, nd6_lle_event, NULL, EVENTHANDLER_PRI_ANY); iflladdr_event_eh = EVENTHANDLER_REGISTER(iflladdr_event, @@ -1929,7 +1931,7 @@ struct mbuf *chain = NULL; u_char linkhdr[LLE_MAX_LINKHDR]; size_t linkhdrsize; - int lladdr_off; + int linked, lladdr_off; NET_EPOCH_ASSERT(); IF_AFDATA_UNLOCK_ASSERT(ifp); @@ -1976,8 +1978,14 @@ LLE_WLOCK(ln); /* Prefer any existing lle over newly-created one */ ln_tmp = nd6_lookup(from, LLE_EXCLUSIVE, ifp); - if (ln_tmp == NULL) - lltable_link_entry(LLTABLE6(ifp), ln); + if (ln_tmp == NULL) { + linked = lltable_link_entry(LLTABLE6(ifp), ln); + if (linked < 0) { + IF_AFDATA_WUNLOCK(ifp); + lltable_free_entry(LLTABLE6(ifp), ln); + return; + } + } IF_AFDATA_WUNLOCK(ifp); if (ln_tmp == NULL) { /* No existing lle, mark as new entry (6,7) */ @@ -2285,7 +2293,7 @@ { struct llentry *lle = NULL, *lle_tmp; struct in6_addr *psrc, src; - int send_ns, ll_len; + int linked, send_ns, ll_len; char *lladdr; NET_EPOCH_ASSERT(); @@ -2320,8 +2328,12 @@ /* Prefer any existing entry over newly-created one */ lle_tmp = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); if (lle_tmp == NULL) - lltable_link_entry(LLTABLE6(ifp), lle); + linked = lltable_link_entry(LLTABLE6(ifp), lle); IF_AFDATA_WUNLOCK(ifp); + if (lle_tmp == NULL && linked < 0) { + lltable_free_entry(LLTABLE6(ifp), lle); + lle = NULL; + } if (lle_tmp != NULL) { lltable_free_entry(LLTABLE6(ifp), lle); lle = lle_tmp; @@ -2506,6 +2518,7 @@ struct ifnet *ifp; struct llentry *ln, *ln_tmp; struct sockaddr *dst; + int linked; ifp = ia->ia_ifa.ifa_ifp; if (nd6_need_cache(ifp) == 0) @@ -2521,9 +2534,14 @@ LLE_WLOCK(ln); /* Unlink any entry if exists */ ln_tmp = lla_lookup(LLTABLE6(ifp), LLE_EXCLUSIVE, dst); + linked = lltable_link_entry(LLTABLE6(ifp), ln); + if (linked < 0) { + IF_AFDATA_WUNLOCK(ifp); + lltable_free_entry(LLTABLE6(ifp), ln); + return (ENOBUFS); + } if (ln_tmp != NULL) lltable_unlink_entry(LLTABLE6(ifp), ln_tmp); - lltable_link_entry(LLTABLE6(ifp), ln); IF_AFDATA_WUNLOCK(ifp); if (ln_tmp != NULL) Index: sys/netinet6/nd6_rtr.c =================================================================== --- sys/netinet6/nd6_rtr.c +++ sys/netinet6/nd6_rtr.c @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,22 @@ VNET_DEFINE_STATIC(struct nd6_drhead, nd6_defrouter); #define V_nd6_defrouter VNET(nd6_defrouter) +/* + * Global limit and per-VNET count for the maximum amount of default routers + * we accept in the list. + */ +static int nd6_defrouter_max = 16; +VNET_DEFINE_STATIC(int, nd6_defrouter_cur); +#define V_nd6_defrouter_cur VNET(nd6_defrouter_cur) + +/* + * Global limit and per-VNET count for the maximum amount of prefixes + * we accept in the list. + */ +static int nd6_prefixes_max = 64; +VNET_DEFINE_STATIC(int, nd6_prefixes_cur); +#define V_nd6_prefixes_cur VNET(nd6_prefixes_cur) + VNET_DECLARE(int, nd6_recalc_reachtm_interval); #define V_nd6_recalc_reachtm_interval VNET(nd6_recalc_reachtm_interval) @@ -135,6 +152,7 @@ ND6_WLOCK_ASSERT(); TAILQ_REMOVE(&V_nd6_defrouter, dr, dr_entry); + V_nd6_defrouter_cur--; V_nd6_list_genid++; if (drq != NULL) TAILQ_INSERT_TAIL(drq, dr, dr_entry); @@ -1122,16 +1140,31 @@ } } + /* + * Do this check before possibly removing the router from the list. + * If the sysctl is set to 0 we accept an unrestricted amount of + * default routers. If people do not want default routers, they + * should use the -no_radr interface flags or sysctl. + */ + if (nd6_defrouter_max > 0 && V_nd6_defrouter_cur >= nd6_defrouter_max) { + ND6_WUNLOCK(); + ICMP6STAT_INC(icp6s_overflowdefrtr); + return (NULL); + } + V_nd6_defrouter_cur++; + if (dr != NULL) { /* * The preferred router may have changed, so relocate this * router. */ TAILQ_REMOVE(&V_nd6_defrouter, dr, dr_entry); + V_nd6_defrouter_cur--; n = dr; } else { n = malloc(sizeof(*n), M_IP6NDP, M_NOWAIT | M_ZERO); if (n == NULL) { + V_nd6_defrouter_cur--; ND6_WUNLOCK(); return (NULL); } @@ -1393,7 +1426,14 @@ IN6_MASK_ADDR(&new->ndpr_prefix.sin6_addr, &new->ndpr_mask); ND6_WLOCK(); + if (nd6_prefixes_max > 0 && V_nd6_prefixes_cur >= nd6_prefixes_max) { + ND6_WUNLOCK(); + ICMP6STAT_INC(icp6s_overflowprfx); + free(new, M_IP6NDP); + return (ENOBUFS); + } LIST_INSERT_HEAD(&V_nd_prefix, new, ndpr_entry); + V_nd6_prefixes_cur++; V_nd6_list_genid++; ND6_WUNLOCK(); @@ -1434,6 +1474,7 @@ ND6_WLOCK_ASSERT(); LIST_REMOVE(pr, ndpr_entry); + V_nd6_prefixes_cur--; V_nd6_list_genid++; if (list != NULL) LIST_INSERT_HEAD(list, pr, ndpr_entry); @@ -2639,3 +2680,15 @@ CTLTYPE_OPAQUE | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, nd6_sysctl_drlist, "S,in6_defrouter", "NDP default router list"); + +/* + * These ones are not adjustable per-VNET to keep things simple and avoid + * VNETs allowing themselves to be a DoS target exhausting memory. Otherwise + * we need to make this a PROC as well and handle a global max cap per-vnet. + */ +SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, nd6_defrouter_max, + CTLFLAG_RW, &nd6_defrouter_max, 0, + "Maximum number of IPv6 default routers. 0 accept unlimited."); +SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, nd6_prefixes_max, + CTLFLAG_RW, &nd6_prefixes_max, 0, + "Maximum number of IPv6 prefix list entries. 0 accept unlimited."); Index: usr.bin/netstat/inet6.c =================================================================== --- usr.bin/netstat/inet6.c +++ usr.bin/netstat/inet6.c @@ -1055,6 +1055,14 @@ "{N:/bad router advertisement message%s}\n"); p(icp6s_badredirect, "\t{:bad-redirect/%ju} " "{N:/bad redirect message%s}\n"); + p_5(icp6s_overflowdefrtr, "\t{:too-many-default-routers/%ju} " + "{N:/too many default routers}\n"); + p_5(icp6s_overflowprfx, "\t{:too-many-prefixes/%ju} " + "{N:/too many prefixes}\n"); + p_5(icp6s_overflownndp, "\t{:too-many-neighbour-entries/%ju} " + "{N:/too many neighbour entries}\n"); + p_5(icp6s_overflowredirect, "\t{:too-many-redirects/%ju} " + "{N:/too many redirects}\n"); xo_close_container("errors"); p(icp6s_pmtuchg, "\t{:path-mtu-changes/%ju} {N:/path MTU change%s}\n"); #undef p